package bison;

import bison.LexToken;

class LexerBase
{
   var inputString:String;
   var inputPos:Int;

   var eos:Bool;
   var lvalue:Dynamic;
   var identValue:String;
   var errValue:String;

   public var keyWords:Map<String,Int>;
   public var ignoreLinefeed:Bool;
   public var consumeNewline:Bool;
   public var parseHex:Bool;
   public var tokString:Int;
   public var tokFloat:Int;
   public var tokInt:Int;
   public var tokRegex:Int;
   public var tokIdent:Int;
   public var tokPlusPlus:Int;
   public var tokMinusMinus:Int;
   public var tokMinusEq:Int;
   public var tokLtEq:Int;
   public var tokLtLt:Int;
   public var tokLtLtEq:Int;
   public var tokGtGt:Int;
   public var tokGtGtGt:Int;
   public var tokGtEq:Int;
   public var tokGtGtEq:Int;
   public var tokGtGtGtEq:Int;
   public var tokEqEq:Int;
   public var tokBangEq:Int;
   public var tokStarEq:Int;
   public var tokSlashEq:Int;
   public var tokPercentEq:Int;
   public var tokPlusEq:Int;
   public var tokTildeEq:Int;
   public var tokAmpAmp:Int;
   public var tokBarBar:Int;
   public var tokAmpEq:Int;
   public var tokBarEq:Int;
   public var tokHatHat:Int;
   public var tokHatEq:Int;
   public var tokDotDotDot:Int;
   public var tokMinusGt:Int;


   public var hashStart:Bool;


   var fileName:String;
   var line:Int;
   var col:Int;

   var lastLine:Int;
   var lastCol:Int;

   var  comment:String;
   public var parseSlashStarComments:Bool;
   public var parseSlashSlashComments:Bool;

   var beginPos:Position;
   var endPos:Position;


   public function new(inString:String="",inFileName="")
   {
      ignoreLinefeed = true;
      consumeNewline = false;

      tokString = 0;
      tokFloat  = 0;
      tokInt    = 0;
      tokRegex  = 0;
      tokIdent  = 0;
      tokInt    = 0;

      tokPlusPlus = 0;
      tokPlusEq = 0;
      tokMinusMinus = 0;
      tokMinusEq = 0;

      tokLtLt = 0;
      tokLtEq = 0;
      tokLtLtEq = 0;

      tokGtGt = 0;
      tokGtGtEq = 0;
      tokGtGtGt = 0;
      tokGtGtGtEq = 0;
      tokGtEq = 0;

      tokEqEq = 0;
      tokBangEq = 0;
      tokStarEq = 0;
      tokSlashEq = 0;
      tokPercentEq = 0;
      tokTildeEq = 0;

      tokAmpAmp = 0;
      tokAmpEq = 0;
      tokBarBar = 0;
      tokBarEq = 0;
      tokHatHat = 0;
      tokHatEq = 0;
      tokDotDotDot = 0;

      parseHex  = true;
      parseSlashSlashComments = true;
      parseSlashStarComments = true;

      keyWords = new Map<String,Int>();

      hashStart = false;

      lastLine = lastCol = 0;
      line = 1;

      setString(inString,inFileName);
   }

   public function setString(inString:String,inFileName="")
   {
      inputString = inString;
      inputPos = 0;

      eos = false;

      lvalue = null;

      fileName  = inFileName;
      col = 1;
      beginPos = currentPosition();
      endPos = currentPosition();
   }

   public function setInput(inInput:haxe.io.Input,inFileName="")
   {
      setString(inInput.readAll(8192).toString(),inFileName);
   }


   /**
    * Method to retrieve the semantic value of the last scanned token.
    * @return the semantic value of the last scanned token.  */
   public function getLVal () : Dynamic
   {
      return lvalue;
   }

   function currentPosition()
   {
      return new Position(line,col,fileName);
   }

   /**
    * Entry point for the scanner.  Returns the token identifier corresponding
    * to the next token and prepares to return the semantic value
    * of the token.
    * @return the token identifier corresponding to the next token. */
   public function yylex() : Int
   {
      var result = do_yylex();
      return result;
   }


   public function do_yylex() : Int
   {
      lvalue = null;
      
      while(true)
      {
         beginPos.line = endPos.line = line;
         beginPos.col  = endPos.col  = col;
         var tok = nextToken();
         if (tok>=0)
            return tok;
            
         switch(tok)
         {
            case LexToken.EOS: return 0;
            case LexToken.SPACE: // Go around again ...
            case LexToken.ERROR: return onLexerError(errValue);
            case LexToken.IDENT: return processIdent(identValue);
            case LexToken.STRING:
                   if (tokString>0)
                      return tokString;
                   return onLexerError("String token not implemented");
            case LexToken.COMMENT:
                   //comment += value;
                   // Go around again
            case LexToken.INT:
                   if (tokInt>0)
                      return tokInt;
                   return onLexerError("Int token not implemented");
            case LexToken.FLOAT:
                   if (tokFloat>0)
                      return tokFloat;
                   return onLexerError("Float token not implemented");
 
            case LexToken.REGEX:
                   if (tokRegex>0)
                      return tokRegex;
                   return onLexerError("Regex token not implemented");
         }
      }
      return 0;
   }

   function onLexerError(message:String)
   {
      yyerror(message);
      return 1;
   }

   function processIdent(name:String) : Int
   {
      lvalue = name;
      if (keyWords.exists(name))
         return keyWords.get(name);
      if (tokIdent<=0)
         onLexerError("Lexer ident not implemented");
      return tokIdent;
   }

   /**
    * Entry point for error reporting.  Emits an error
    * in a user-defined way.
    *
    * 
    * @param s The string for the error message.  */
   public function yyerror(message:String, ?loc:Dynamic ) : Void
   { 
       #if cpp
       cpp.Lib.println("**** " + message + (loc==null ? "" : " @ " + loc));
       #else
       neko.Lib.println("**** " + message + (loc==null ? "" : " @ " + loc));
       #end
   }

   /**
     * Method to retrieve the beginning position of the last scanned token.
     * @return the position at which the last scanned token starts.  */
   public function getStartPos():Position
   {
       return beginPos;
   }

    /**
     * Method to retrieve the ending position of the last scanned token.
     * @return the first position beyond the last scanned token.  */
   public function getEndPos():Position
   {
      endPos.line = lastLine;
      endPos.col = lastCol;
      return endPos;
   }




   static inline var dot:Int = '.'.code;
   static inline var bslash:Int = '\\'.code;
   static inline var slash:Int = '/'.code;
   static inline var quote:Int = '"'.code;
   static inline var squote:Int = '\''.code;
   static inline var lessThan:Int = '<'.code;
   static inline var greaterThan:Int = '>'.code;
   static inline var equals:Int = '='.code;
   static inline var bang:Int = '!'.code;
   static inline var hash:Int = '#'.code;
   static inline var percent:Int = '%'.code;
   static inline var star:Int = '*'.code;
   static inline var bar:Int = '|'.code;
   static inline var amp:Int = '&'.code;
   static inline var hat:Int = '^'.code;
   static inline var plus:Int = '+'.code;
   static inline var minus:Int = '-'.code;
   static inline var tilde:Int = '~'.code;
   static inline var a_code:Int = 'a'.code;
   static inline var f_code:Int = 'f'.code;
   static inline var n_code:Int = 'n'.code;
   static inline var r_code:Int = 'r'.code;
   static inline var t_code:Int = 't'.code;
   static inline var x_code:Int = 'x'.code;
   static inline var z_code:Int = 'z'.code;
   static inline var A_code:Int = 'A'.code;
   static inline var F_code:Int = 'F'.code;
   static inline var Z_code:Int = 'Z'.code;
   static inline var __code:Int = '_'.code;
   static inline var num0_code:Int = '0'.code;
   static inline var num9_code:Int = '9'.code;
   static inline var space_code:Int = ' '.code;
   static inline var tab_code:Int = '\t'.code;
   static inline var new_line:Int = '\n'.code;
   static inline var line_feed:Int = '\r'.code;

   function nextByte() : Int
   {
      if (inputPos<inputString.length)
      {
         lastCol = col;
         lastLine = line;
         #if cpp
         var result:Int = untyped inputString.cca(inputPos++);
         #else
         var result:Int = inputString.charCodeAt(inputPos++);
         #end
         if (result!=line_feed)
            col++;
         if (result==new_line)
         {
            col = 1;
            line++;
         }
         return result;
      }
      eos = true;
      return -1;
   }

   inline function unget(inChar:Int)
   {
      inputPos--;
      col = lastCol;
      line = lastLine;
   }

   function parseComment(toStarSlash:Bool)
   {
      var pos0 = inputPos;
      while(true)
      {
         var c = nextByte();
         if (toStarSlash)
         {
            if (c==star)
            {
               var next = nextByte();
               if (next==slash)
                 break;
               unget(next);
            }
         }
         else
         {
            if ( c==new_line || c==line_feed)
               break;
         }
         //result+=c;
      }

      lvalue = inputString.substr(pos0,inputPos-pos0);
      return LexToken.COMMENT;
   }

   public function nextToken() : Int
   {
      while(!eos)
      {
         var x = nextByte();
         if (!eos)
         {
            if (ignoreLinefeed && x==line_feed)
            {
               // Ignore
            }
            else if ( (x>=a_code && x<=z_code) || (x>=A_code && x<=Z_code) || x==__code || (hashStart && x==hash) )
            {
               var pos0 = inputPos-1;
               while(true)
               {
                  x = nextByte();
                  
                  if ( (x>=a_code && x<=z_code) || (x>=A_code && x<=Z_code) || x==__code ||
                           (x>=num0_code && x<=num9_code) )
                  {
                        // ok
                  }
                  else
                  {
                     unget(x);
                     break;
                  }
               }
               identValue = inputString.substr(pos0,inputPos-pos0);
               return LexToken.IDENT;
            }
            else if (x==space_code || x==tab_code ||
                       (consumeNewline && ( x==new_line || x==line_feed) ) )
            {
               do
               {
                  x = nextByte();
               } while(x==space_code || x==tab_code ||
                       (consumeNewline && ( x==new_line || x==line_feed) ) );
               unget(x);
               return LexToken.SPACE;
            }
            else if (x==lessThan)
            {
               var next = nextByte();
               if (next==equals)
               {
                  if (tokLtEq>0) return tokLtEq;
               }
               else if (next==lessThan)
               {
                  if (tokLtLt>0)
                  {
                     next = nextByte();
                     if (tokLtLtEq>0 && next==equals)
                        return tokLtLtEq;
                     unget(next);
                     return tokLtLt;
                  }
               }
               unget(next);
               return x;
            }
            else if (x==greaterThan)
            {
               var next = nextByte();
               if (next==equals)
               {
                  if (tokGtEq>0) return tokGtEq;
               }
               else if (next==greaterThan)
               {
                  if (tokGtGt>0)
                  {
                     next = nextByte();
                     if (tokGtGtEq>0 && next==equals)
                        return tokGtGtEq;
                     if (tokGtGtGt>0 && next==greaterThan)
                     {
                        next = nextByte();
                        if (tokGtGtGtEq>0 && next==equals)
                           return tokGtGtGtEq;
                        unget(next);
                        return tokGtGtGt;
                     }
                     unget(next);
                     return tokGtGt;
                  }
               }
               unget(next);
               return x;
            }
            else if ( x==plus || x==minus )
            {
                var next = nextByte();
                if (next==x)
                {
                   if (x==plus && tokPlusPlus>0)
                      return tokPlusPlus;
                   if (x==minus && tokMinusMinus>0)
                      return tokMinusMinus;
                }
                else if (next==equals)
                {
                   if (x==plus && tokPlusEq>0)
                      return tokPlusEq;
                   if (x==minus && tokMinusEq>0)
                      return tokMinusEq;
                }
                else if (x==minus && next==greaterThan && tokMinusGt>0)
                   return tokMinusGt;
                unget(next);
                return x;
            }
            else if ( x==equals || x==bang || x==star || x==percent || x==tilde)
            {
                var next = nextByte();
                if (next==equals)
                {
                   if (x==equals && tokEqEq>0) return tokEqEq;
                   if (x==bang && tokBangEq>0) return tokBangEq;
                   if (x==star && tokStarEq>0) return tokStarEq;
                   if (x==percent && tokPercentEq>0) return tokPercentEq;
                   if (x==tilde && tokTildeEq>0) return tokTildeEq;
                }
                unget(next);
                return x;
            }
            else if ( x==amp )
            {
                var next = nextByte();
                if (next==equals && tokAmpEq>0)
                   return tokAmpEq;
                if (next==amp && tokAmpAmp>0)
                   return tokAmpAmp;
                unget(next);
                return x;
            }
            else if ( x==bar )
            {
                var next = nextByte();
                if (next==equals && tokBarEq>0)
                   return tokBarEq;
                if (next==bar && tokBarBar>0)
                   return tokBarBar;
                unget(next);
                return x;
            }
            else if ( x==hat )
            {
                var next = nextByte();
                if (next==equals && tokHatEq>0)
                   return tokHatEq;
                if (next==bar && tokHatHat>0)
                   return tokHatHat;
                unget(next);
                return x;
            }
            else if (x==squote || x==quote)
            {
               var pos0 = inputPos;
               var invalid_slash:Position = null;
               while(true)
               {
                  var ch = nextByte();

                  if (ch==bslash)
                  {
                     ch = nextByte();
                     switch(ch)
                     {
                        case bslash: // Ok
                        case squote: // Ok
                        case quote: // Ok
                        case n_code: ch = new_line;
                        case r_code: ch = line_feed;
                        case t_code: ch = tab_code;
                        default:
                          if (invalid_slash==null)
                             invalid_slash = currentPosition();
                     }
                  }
                  else if (ch==-1)
                  {
                     errValue = "Unterminated string";
                     return LexToken.ERROR;
                  }
                  else if (ch==x)
                  {
                     if (invalid_slash!=null)
                     {
                        beginPos = invalid_slash;
                        errValue =  "Error invalid backslash quote";
                        return LexToken.ERROR;
                     }
                     lvalue = inputString.substr(pos0,inputPos-pos0-1);
                     return LexToken.STRING;
                  }
               }
            }
            else if ( (x>=num0_code && x<=num9_code) || x==dot )
            {
               var pos0 = inputPos-1;
               var first_dot = x==dot ? pos0 : -1;
               // TODO: exponetial/hex notation
               while(true)
               {
                  x = nextByte();
                  var is_dot = x==dot;
                  if (first_dot>=0 && is_dot)
                  {
                     // Not two in a row ...
                     if (first_dot!=inputPos-2)
                        break;
                     
                     // Two in a row - how about 3 ?
                     var next = nextByte();
                     if (next==dot)
                     {
                        // Found just '...'
                        if (inputPos-pos0==3)
                           return tokDotDotDot;
                        // Erase back 3 ...
                        inputPos -= 3;
                        col-=3;

                        lvalue = Std.parseInt(inputString.substr(pos0,inputPos-pos0));
                        return LexToken.INT;
                     }
                     else
                     {
                        errValue = "Unknown sequence " + inputString.substr(pos0,inputPos-pos0);
                        return LexToken.ERROR;
                     }
                  }

                  if (parseHex &&  inputPos-pos0 ==2 && x==x_code)
                  {
                     pos0 = inputPos;
                     var result = 0;
                     while(true)
                     {
                        x = nextByte();
                        if (x>=num0_code && x<=num9_code)
                           result = (result<<4) + x - num0_code;
                        else if (x>=a_code && x<=f_code)
                           result = (result<<4) + x + 10 - a_code;
                        else if (x>=A_code && x<=F_code)
                           result = (result<<4) + x + 10 - A_code;
                        else
                           break;
                     }
                     unget(x);
                     if (inputPos-pos0==0) // Naked 0x ?
                     {
                        errValue = "Missing hex constant";
                        return LexToken.ERROR;
                     }
                     lvalue = result;
                     return LexToken.INT;
                  }

                  if ( (x>=num0_code && x<=num9_code) || is_dot )
                  {
                     if (is_dot)
                        first_dot = inputPos-1;
                  }
                  else
                     break;
               }

               unget(x);
               if (inputPos-pos0==1 && first_dot>=0)
                  return dot;
               if (first_dot>=0)
               {
                  lvalue = Std.parseFloat(inputString.substr(pos0,inputPos-pos0));
                  return LexToken.FLOAT;
               }
               else
               {
                  lvalue = Std.parseInt(inputString.substr(pos0,inputPos-pos0));
                  return LexToken.INT;
               }
            }
            else if (x==slash)
            {
               if (parseSlashStarComments || parseSlashSlashComments)
               {
                  var next = nextByte();
                  if (parseSlashSlashComments && next==star)
                     return parseComment(true);
                  else if (parseSlashSlashComments && next==slash)
                     return parseComment(false);
                  unget(next);
               }
               return x;
            }
            else
               return x;
         }
      }
   
      return LexToken.EOS;
   }
   
}


